// KB_Sorting.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include "getopt.h"
#include "kbsort.h"

/**
* Config area.... start....
**/

#define DEBUG	0
#define USB	0
#define NB	1

#define MULTI 1
#define SINGLE  0
#define THREAD 0

HHOOK hk=NULL;
UINT_PTR hTimer = NULL;

#if THREAD
	CRITICAL_SECTION g_cs;
#endif

#define MAX_KEYS 80

int PressCnt[MAX_KEYS]={0};

int test_key_num = 80 ;
int	MAX_PASS = 50;
int TIMEOUT	= 10;

int gCnt = 0;
int timeClick=0;

int isExit = 0;
int isSetMAX_PASS=0;
int isTimeOut=0; 
int verbose=0;
int isPassed=0;

int timerEnable=0;

const int IDT_TIMER1 = 0;

char *fName = NULL;

/**
* Config area.... Ending....
**/

int MAXSCANCODE=sizeof(kbtest)/sizeof(struct kbstore);


char *Key_Table[MAX_KEYS]={	"1","2","3","4","5","6","7","8","9","0","-","=","Backsp","NumLock","/","*","-",
						"Tab","Q","W","E","R","T","Y","U","I","O","P","[","]","|","7","8","9","+",
						"CAPS","A","S","D","F","G","H","J","K","L",":","\"","Enter","4","5","6",
						"LSHIFT","Z","X","C","V","B","N","M","<",">","?","RSHIFT","UP","1","2","3","Enter",
						"LCTRL","Fn","ALT","SPACE","ALT","Menu","RCTRL","LEFT","DOWN","RIGHT","0","."};

#if NB
	int scancode[MAX_KEYS]={ 2,3,4,5,6,7,8,9,10,11,12,13,14,69,53,55,74,
							15,16,17,18,19,20,21,22,23,24,25,26,27,43,71,72,73,78,
							58,30,31,32,33,34,35,36,37,38,39,40,28,75,76,77,
							42,44,45,46,47,48,49,50,51,52,53,54,72,79,80,81,28,
							//54,44,45,46,47,48,49,50,51,52,53,42,72,79,80,81,28,
							29,120,56,57,56,93,29,75,80,77,82,83
							//29,92,56,57,56,93,77,75,80,77,82,83
						};

#endif

#if USB
	int scancode[MAX_KEYS]={ 2,3,4,5,6,7,8,9,10,11,12,13,14,69,53,55,74,
							15,16,17,18,19,20,21,22,23,24,25,26,27,43,71,72,73,78,
							58,30,31,32,33,34,35,36,37,38,39,40,28,75,76,77,
							//42,44,45,46,47,48,49,50,51,52,53,54,72,79,80,81,28,
							54,44,45,46,47,48,49,50,51,52,53,42,72,79,80,81,28,
							//29,120,56,57,56,93,29,75,80,77,82,83
							29,92,56,57,56,93,77,75,80,77,82,83
						};
#endif

void uExit(int ExitCode)
{
	if(hk!=NULL){
		UnhookWindowsHookEx(hk);
	}

	if (hTimer!=NULL){
		if(!KillTimer(NULL, hTimer)){
			printf("\n[E] Timer kill failed.\n");
		}
	}

	#if THREAD
		DeleteCriticalSection(&g_cs);
	#endif
	
	exit(ExitCode);
}

DWORD WINAPI doThread(PVOID lParam)
{
	KBSCAN data;
	KBSCAN *p;
	p=&data;

	memcpy(&data, (KBSCAN *)lParam, sizeof(KBSCAN));

	#if THREAD
		EnterCriticalSection(&g_cs);
		//printf("\nThread: Scancode: %d   Flag: %d\n", p->scanCode, p->flags);
	#endif	
	
	int j=0;
	for (j=0;j<MAXSCANCODE; j++){
		if (p->scanCode == kbtest[j].sc){
			switch(kbtest[j].keyNum){
				case 1:
					kbtest[j].Count++;
					break;

				case 2:
					if (p->flags > 0 && p->flags < 32  && kbtest[j].flag == 1){
						kbtest[j].Count++;
					}
					if (p->flags == 0 && kbtest[j].flag == 0){
						kbtest[j].Count++;
					}

					//Alt Flag
					if (p->flags == 32 && kbtest[j].flag == 0){
						kbtest[j].Count++; //Alt key left
					}

					if (p->flags == 33 && kbtest[j].flag == 1){
						kbtest[j].Count++; //Alt key right
					}
					break;
			}
	
			#if DEBUG						
				printf("%s\t\t%d\t%d\n", kbtest[j].Keys, kbtest[j].Count,p->flags);
			#endif
			if (verbose){
				printf("%s\t\t%d\t%s\t%d\n", kbtest[j].Keys, kbtest[j].Count, kbtest[j].Count >= MAX_PASS?"Pass":"=", p->flags);
			}

			if (checkResult())	{
				isExit = 1;	
				Sleep(500);  // delay for exit...
				uExit(0);
			}
		}
	}

	#if THREAD
		LeaveCriticalSection(&g_cs);
	#endif

	return 0;
}

LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
	PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;

	if (code < 0){ // do not process message  
        return CallNextHookEx(hk, code, wParam, lParam);
	}

	switch(wParam){
		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
		//case WM_KEYUP:
		//case WM_SYSKEYUP:

		#if THREAD
			DWORD dwThreadId;  
     		HANDLE hThread = NULL;   // thread handle
			KBSCAN scan;
			
			scan.scanCode = p->scanCode;
			scan.flags = p->flags;
			
			hThread = CreateThread(NULL, 0, doThread, (PVOID)&scan, 0, &dwThreadId);
			if(hThread==NULL){
				printf("[E] CreateThread Failed.\n");
				exit(0);
			}
			//printf("\nThread ID: %d\n",dwThreadId);
		#endif

		#if MULTI
			int j=0;
			for (j=0;j<MAXSCANCODE; j++){
				if (p->scanCode == kbtest[j].sc){
					switch(kbtest[j].keyNum){
						case 1:
							kbtest[j].Count++;
							break;

						case 2:
							if (p->flags > 0 && p->flags < 32  && kbtest[j].flag == 1){
								kbtest[j].Count++;
							}
							if (p->flags == 0 && kbtest[j].flag == 0){
								kbtest[j].Count++;
							}

							//Alt Flag
							if (p->flags == 32 && kbtest[j].flag == 0){
								kbtest[j].Count++; //Alt key left
							}

							if (p->flags == 33 && kbtest[j].flag == 1){
								kbtest[j].Count++; //Alt key right
							}
							break;
					}
		
						#if DEBUG						
							printf("%s\t\t%d\t%d\n", kbtest[j].Keys, kbtest[j].Count,p->flags);
						#endif
						if (verbose){
							printf("%s\t\t%d\t%s\t%d\n", kbtest[j].Keys, kbtest[j].Count, kbtest[j].Count >= MAX_PASS?"Pass":"=", p->flags);
						}

						// here count pressed keys.
						checkResult();

						//if (checkResult())	{
						//	isExit = 1;	
						//	Sleep(500);  // delay for exit...
						//	uExit(0);   // 
						//}
				}
			}
		#endif

		#if SINGLE
			
			#if DEBUG
				printf("[Debug] KEYBOARD - vk: %d, scancode: %d,  flag: %d, ExtInfo %d\n", p->vkCode, p->scanCode, p->flags, p->dwExtraInfo); 
			#endif
			if (gCnt >= MAX_KEYS){
				isExit = 1;	
				uExit(0);
			}

			if (p->scanCode == scancode[gCnt]){
				printf("%s\t\t%d\r", Key_Table[gCnt], ++PressCnt[gCnt]);
				if(PressCnt[gCnt]>=MAX_PASS){
					printf("%s\t|\t%d\t|\tPass",Key_Table[gCnt], PressCnt[gCnt]);
					gCnt++;
					printf("\n");
				}
			}else if (p->scanCode == 91){
					printf("%s\t|\t%d\t|\tFail",Key_Table[gCnt], PressCnt[gCnt]);
					gCnt++;
					printf("\n");
			}
		#endif

	}
	return 1;
}


void CALLBACK TimeProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)  
{  
    timeClick++;
    if (timeClick>TIMEOUT && isPassed == 0){
		isTimeOut=1;
		printFailKeys(); 
	}else if (timeClick>TIMEOUT && isPassed == 1){
		printf("\nKeyboard test passed!\n");
		dumpToFile(fName, true); 
		uExit(0); // pass code
	}
}

void dump()
{
	int i=0;
	for (i=0;i<MAX_KEYS;i++)
		printf("%d  %s %d\n",i, Key_Table[i],scancode[i]);
}

void dumpToFile(char *fName, int result)
{
	FILE *f=fopen(fName, "w");
	int i=0;

	if(!result){
		if (f != NULL){
			fprintf(f, "Error code: KB0018\nFailed Keys:\n");
			for (i=0;i<MAXSCANCODE;i++){
				if ( kbtest[i].Count != MAX_PASS )
					fprintf(f, "     %s\t\t%d\n",kbtest[i].Keys, kbtest[i].Count);
			} 
		}
	} else {
		fprintf(f, "PASS\n");
	}

	if (f != NULL) {
		fclose(f);
	}
}

int checkIsFailed()
{
	int i=0;
	for (i=0;i<MAXSCANCODE;i++)
		if ( kbtest[i].Count > 0 && kbtest[i].Count != MAX_PASS)
			return 1;

	return 0;
}

void printFailKeys()
{
	int i=0;
	printf("Error code: KB0018\nFailed Keys:\n");
	for (i=0;i<MAXSCANCODE;i++){
		if ( kbtest[i].Count != MAX_PASS)
			printf("     %s\t\t%d\n",kbtest[i].Keys, kbtest[i].Count);
	} 

	if (fName!=NULL)
		dumpToFile(fName, false);

	uExit(1);
} 

int checkResult()
{
	int i=0;
	int PressedKeys=0;
	int PassCount = 0; 

	for (i=0; i<MAXSCANCODE; i++){
		if (kbtest[i].Count>=1){
			PressedKeys++;
		}

		if (kbtest[i].Count>=MAX_PASS){
			PassCount++;
		}
	}

	if(!verbose)
		printf("Pressed: %d\t Passed: %d\r", PressedKeys, PassCount);
	
	if ( PassCount >= test_key_num ){
		if(!checkIsFailed()){
			if(!timerEnable){
				printf("\nKeyboard test passed!\n");
				uExit(0);
			}
			isPassed=1;
		}
	}

	return 0;
}

void usage()
{
	printf("kb_test.exe [-c <Cycle>] [-t <timer>] [-k <keyNum>] [-f <file>] [-v] [-h]\n");
	printf("Option:\n");
	printf("\t-c <Cycle>   Set one key test loops\n");
	printf("\t-t <timer>   Set timeout timers\n");
	printf("\t-k <keyNum>  Set how many keys need to be tested\n");
	printf("\t-f <file>    Show results to file\n");
	printf("\t-h           Show this help file.\n");
	printf("\t-v           Show detail debug information.\n");
	
	uExit(1);
}

int main(int argc, char* argv[])
{
	int i=0;
	int ch;
	
	while((ch = getopt(argc, argv, "c:t:k:hvf:"))!= -1){
		switch(ch){
			case 'c':
			case 'C':
				MAX_PASS = atoi(optarg);
				isSetMAX_PASS=1;
				break;

			case 't':
			case 'T':
				TIMEOUT = atoi(optarg);
				if (TIMEOUT!=0){
					timerEnable=1;
				}
				break;
				
			case 'k':
			case 'K':
				test_key_num = atoi(optarg);
				break;
				
			case 'V':
			case 'v':
				verbose=1;
				break;
			case 'F':
			case 'f':
				fName=optarg;
				break;
			case 'h':			
			case 'H':
			case '?':
				usage();
				break;
		} 	
	}
	
	#if DEBUG
		dump();
	#endif
	
	system("title Keyboard Sorting Utilty        Rev: 1.4"); 
	
	if (timerEnable){
		hTimer=SetTimer(NULL, IDT_TIMER1, 1000, TimeProc);
		if (hTimer==NULL){
			printf("[E] SetTimer() %d\n", GetLastError());
		}
	}

	printf("ME Keyboard Sorting Tool for Multi Keys...\n");
	printf("Parameters: Timer: %d(%#x)  Keys: %d  Cycle: %d \nNow start testing...\n",TIMEOUT, hTimer, test_key_num, MAX_PASS);
	printf("----------------------------------------------\n");
	printf("Keys\t\t|\tCount\t|\tResults\n");
	printf("----------------------------------------------\n");

#if THREAD
	InitializeCriticalSection(&g_cs);
#endif

	hk=SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc,NULL, 0);
	if (hk==NULL){
		printf("[E] SetWindowsHookEx() %d\n", GetLastError());
		return 1;
	}

	MSG msg;  
    while (GetMessage(&msg, NULL, 0, 0))  
    {  
        if (!TranslateAccelerator(msg.hwnd, NULL, &msg))  
        {  
            TranslateMessage(&msg);  
            DispatchMessage(&msg);  
        } else if(gCnt >= MAX_KEYS)
			break;
	}

	if (hk != NULL){
		UnhookWindowsHookEx(hk);
	}

	return 0;
}